﻿using System;
using System.ServiceModel;
using Microsoft.Xrm.Sdk;
using System.Reflection;
using System.Diagnostics;
using System.Threading;

namespace Fine.CRM.Common
{
    public static class StackDumpExtension
    {
        public static string Dump(this System.Collections.Stack stack)
        {
            string returnValue = string.Empty;

            foreach (object item in stack)
            {
                string value = (string)item;
                if (!string.IsNullOrEmpty(returnValue))
                {
                    returnValue = string.Concat(returnValue, "<-", value);
                }
                else
                {
                    returnValue = value;
                }
            }

            return returnValue;
        }
    }


    public class TraceLog
    {

        public enum MessageType { Information = 765540000, Warning = 765540001, Exception = 765540002 };

        private System.Collections.Stack methodNameStack = new System.Collections.Stack();
        private System.Collections.Generic.List<Entity> logItems = new System.Collections.Generic.List<Entity>();
        private int counter = 0;
        private static object syncRoot = new Object();

        public IOrganizationService OrganizationService { get; private set; }
        public string ProcessName { get; private set; }
        public string ContextId { get; private set; }
        public string EntityLogicalName { get; private set; }
        public string PluginMessageName { get; private set; }
        public EntityReference ExecutingAsUser { get; private set; }
        public ITracingService TracingService { get; set; }
        public bool EnableInformationMessages { get; private set; }
        public string MethodName { get; set; }

        public TraceLog(IOrganizationService service, string contextId, string entityLogicalName, string pluginMessageName, string processName, EntityReference executingAsUser, bool enableInformationMessages)
        {
            this.ExecutingAsUser = null;
            if (executingAsUser != null && executingAsUser.LogicalName == "systemuser")
            {
                this.ExecutingAsUser = executingAsUser;
            }

            this.EntityLogicalName = null;
            if (!string.IsNullOrEmpty(entityLogicalName))
            {
                this.EntityLogicalName = entityLogicalName;
            }

            this.PluginMessageName = null;
            if (!string.IsNullOrEmpty(pluginMessageName))
            {
                this.PluginMessageName = pluginMessageName;
            }

            this.ContextId = null;
            if (!string.IsNullOrEmpty(contextId))
            {
                this.ContextId = contextId;
            }

            this.OrganizationService = service;
            this.ProcessName = processName;
            this.EnableInformationMessages = enableInformationMessages;
        }

        public void EnterMethod(string methodname)
        {
            if(string.IsNullOrEmpty(methodname))
            {
                StackTrace stackTrace = new StackTrace();
                StackFrame stackFrame = stackTrace.GetFrame(1);
                MethodBase methodBase = stackFrame.GetMethod();
                this.MethodName = methodBase.Name;
            }
            else
            {
                this.MethodName = methodname;
            }

            this.MethodName = methodname;
            this.methodNameStack.Push(this.MethodName);
            this.Trace(string.Format("Entered '{0}'.", this.methodNameStack.Dump()));
        }

        public void ExitMethod(string message = null)
        {
            if (!string.IsNullOrEmpty(message))
            {
                this.Trace(message);
            }

            this.Trace(string.Format("Exiting '{0}'.", this.methodNameStack.Dump()));
            if (methodNameStack.Count > 1)
            {
                methodNameStack.Pop();
                this.MethodName = (string)methodNameStack.Peek();
            }
        }

        public void Save()
        {
            try
            {
                if (OrganizationService != null)
                {
                    foreach (Entity record in logItems)
                    {
                        OrganizationService.Create(record);
                    }
                }
            }
            catch (Exception ex)
            { }
        
        }
        
        public void Trace(string message, string methodName, MessageType type)
        {
            try
            {               

                DateTime logTime = DateTime.UtcNow;

                //document message in the plugin trace file.
                if (!string.IsNullOrWhiteSpace(message) && this.TracingService != null)
                {
                    this.TracingService.Trace("{0}, Process: {1}, Method: {2}, {3}", new object[]
						{
							logTime.ToString("yyyy:MM:dd:HH:mm:ss:ffffff"),
							ProcessName,
                            methodName,
							message
						});
                }

                //if this is an information message and information messages are disabled then return now.
                if (!EnableInformationMessages && type == MessageType.Information)
                {
                    return;
                }

                lock (syncRoot)
                {
                    counter++;

                    //document message in the trace log entity.
                    Entity log = new Entity("fine_tracelog");
                    log.Attributes.Add("fine_contextid", this.ContextId);
                    log.Attributes.Add("fine_entityname", this.EntityLogicalName);
                    log.Attributes.Add("fine_messagename", this.PluginMessageName);
                    log.Attributes.Add("fine_executinguserid", this.ExecutingAsUser);
                    log.Attributes.Add("fine_index", logTime.ToString("yyyy:MM:dd:HH:mm:ss:ffffff") + "_" + counter.ToString("D4"));
                    log.Attributes.Add("fine_name", ProcessName);
                    log.Attributes.Add("fine_methodname", methodName);
                    log.Attributes.Add("fine_tracemessage", message);
                    log.Attributes.Add("fine_messagetype", new OptionSetValue((int)type));
                    logItems.Add(log);
                }
               
            }
            catch (Exception ex) {}
        }

        public void Trace(string message, MessageType type)
        {
            Trace(message, this.MethodName, type);
        }

        public void Trace(string message)
        {
            Trace(message, this.MethodName, MessageType.Information);
        }
    }
}
